module WsClient

	class WebSocket
		alias :object_send :send
		
		DEFAULT_VERSION = 0
		
		def initialize(url, options = {})
			@state = State.new
			@mutex = Mutex.new
		
			@protocol = WsClient::ProtocolFactory.create(url, options[:version] || DEFAULT_VERSION)
			@protocol.handshake(options)
			
			@state.goto(:open)
		end
		
		def state
			@state.state
		end
		
		def url
			@protocol.url
		end
		
		def recv
			recv_frame.message
		end
		
		def recv_frame
			catch :closing do
				frame = @protocol.read_frame
				return frame
			end
			
			begin
				close
			rescue WsClient::WebSocketError
			end
			
			raise WsClient::ClosingError.new("Server closed connection")
		end
		
		def send(message, options = {})
			raise WsClient::WebSocketError.new("Can't send to a unopened socket") unless @state.open?
			@protocol.send_message(message, options)
		end
		
		def close
			@mutex.synchronize do
				return unless @state.open?
				@state.goto(:closing)
			end
			
			begin
				@protocol.close
			ensure
				@protocol.close_socket
				@state.goto(:closed)
			end
		end
		
		private
		
		class State
			STATES = [:connecting, :open, :closing, :closed]
			
			def initialize
				@state = 0
			end
			
			def to_s
				state.to_s
			end
			
			def state
				STATES[@state]
			end
			
			def goto(state)
				@state = STATES.index(state) || @state
			end
			
			STATES.each do |state|
				class_eval %Q{
					def #{state}?
						state == :#{state}
					end
				}
			end
		end
	end

=begin
	class WebSocket
		attr_reader :state
		alias :object_send :send
	
		DEFAULT_VERSION = 0
		EVENTS = [:open, :message, :close, :error]
		NOOP = proc {}
	
		def initialize(url, options = {})
			@callbacks = {}
			@callbacks.default = NOOP
			
			@state = State.new
			yield(self) if block_given?
			
			@lock = Mutex.new
			
			t = Thread.new do
				begin
					@protocol = WsClient::ProtocolFactory.create(url, options[:version] || DEFAULT_VERSION)
					@protocol.handshake(options)
				rescue Exception => err
					initiate_close do
						trigger_event(:error, err)
						@protocol.close_socket if @protocol
						
						Thread.exit
					end
				end
			
				@state.goto(:open)
				trigger_event(:open)
			
				catch :closing do
					while true do
						frame = nil
						
						begin
							frame = @protocol.read_frame
						rescue Exception => err
							handle_error(err) { Thread.exit }
						end
						
						trigger_event(:message, frame.message)
					end
				end
				
				shutdown
			end	

			t.abort_on_exception = true
		end
		
		def url
			@protocol.url if @protocol
		end
		
		EVENTS.each do |e|
			class_eval %Q{
				def on#{e}(&block)
					raise ArgumentError.new("No block given") unless block_given?
					@callbacks[:#{e}] = block
				end
			}
		end
		
		def send(message, options = {})
			# Raise exception if not in open state?
			return unless @state.open?
			
			begin
				@protocol.send_message(message, options)
			rescue Exception => err
				# Do cleanup and callbacks in new thread?
				handle_error(err)
			end
		end
		
		def close
			return unless @state.open?
			shutdown
		end
		
		private
		
		def shutdown
			initiate_close do
				@protocol.close rescue nil
				@protocol.close_socket
				
				@state.goto(:closed)
				trigger_event(:close)
			end
		end
		
		def handle_error(err)
			initiate_close do
				trigger_event(:error, err)
								
				@protocol.close_socket
				@state.goto(:closed)
				trigger_event(:close)
				
				yield if block_given?
			end
		end
		
		def initiate_close
			init_close = @lock.synchronize do
				if @state.open? or @state.connecting?
					@state.goto(:closing)
					true
				else
					false
				end
			end
		
			yield if block_given? and init_close
			init_close
		end
		
		def trigger_event(event, *args)
			@lock.synchronize { @callbacks[event].call(*args) rescue nil }
		end
		
		class State
			STATES = [:connecting, :open, :closing, :closed]
			
			def initialize
				@state = 0
			end
			
			def state
				STATES[@state]
			end
			
			def goto(state)
				@state = STATES.index(state) || @state
			end
			
			STATES.each do |state|
				class_eval %Q{
					def #{state}?
						state == :#{state}
					end
				}
			end
		end
	end
=end

end
